fix(generator): escape leading caret in non-negative character class#280
fix(generator): escape leading caret in non-negative character class#280greymoth-jp wants to merge 1 commit into
Conversation
A non-negative character class whose first element is a literal `^` was generated as a bare `[^...]`, which parses back as a negated class. This surfaces through the optimizer: charClassClassrangesMerge sorts class members by code point, so `/[a^]/` becomes `/[^a]/` (`^` is U+005E, before `a`), inverting the regex. Calling generate() on a hand-built or otherwise reordered AST hits the same path. Escape a leading literal `^` when the class is not negative.
|
@greymoth-jp - appreciate the fix. What's your take whether we should be optimizing |
|
Thanks for taking a look. I'd keep the generator fix regardless of what the optimizer does. On your actual question: I agree the optimizer gains nothing by reordering My preference would be to land this generator fix as the correctness floor for |
regexp-tree.optimize('/[a^]/')returns/[^a]/, which inverts the regex:[a^]matchesaor^, while[^a]matches everything excepta.The optimizer's
charClassClassrangesMergesorts character-class members by code point, and^(U+005E) sorts beforea(U+0061), so the class is reordered to put^first. The generator'sCharacterClasshandler emits the members verbatim, so the reordered class is written as[^a], and a leading^in a class is parsed back as negation.It is more visible when the reordered class is short enough for the optimizer to keep:
The same path is reachable through the public
generate()API: building or transforming an AST so that a non-negative class starts with a literal^produces an inverted regex today.The fix is in the generator: when a character class is not negative and its first member is an unescaped literal
^, escape it.[a^]then round-trips correctly, andoptimize('/[a^]/')returns/[a^]/again. Genuine negative classes and an already-escaped[\^a]are unaffected.Tests added for the generator and for the
char-class-classranges-mergetransform. The fullsrcsuite passes.